package cn.ibizlab.util.filter;

import com.fasterxml.jackson.annotation.*;
import org.springframework.data.annotation.Transient;
import org.springframework.util.ObjectUtils;

import java.util.*;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class QueryFilter {

    public static QueryFilter createQuery()
    {
        QueryFilter queryFilter=new QueryFilter();
        return queryFilter;
    }

    public QueryFilter eq(String column,Object value) {
        return op(column,SegmentCond.eq(value));
    }
    public QueryFilter ne(String column,Object value) {
        return op(column,SegmentCond.ne(value));
    }
    public QueryFilter gt(String column,Object value) {
        return op(column,SegmentCond.gt(value));
    }
    public QueryFilter ge(String column,Object value) {
        return op(column,SegmentCond.ge(value));
    }
    public QueryFilter lt(String column,Object value) {
        return op(column,SegmentCond.lt(value));
    }
    public QueryFilter le(String column,Object value) {
        return op(column,SegmentCond.le(value));
    }
    public QueryFilter isnull(String column) {
        return op(column,SegmentCond.isnull());
    }
    public QueryFilter isnotnull(String column) {
        return op(column,SegmentCond.isnotnull());
    }
    public QueryFilter in(String column,String value) {
        if(ObjectUtils.isEmpty(value))
            return this;
        return in(column,Arrays.asList(value.split(";|,|；|，")));
    }
    public QueryFilter in(String column,Collection value) {
        return op(column,SegmentCond.in(value));
    }
    public QueryFilter in(String column,Object value) {
        if(ObjectUtils.isEmpty(value))
            return this;
        if(value instanceof Collection)
            return in(column,(Collection) value);
        else
            return in(column,(String)value);
    }
    public QueryFilter notin(String column,String value) {
        if(ObjectUtils.isEmpty(value))
            return this;
        return notin(column,Arrays.asList(value.split(";|,|；|，")));
    }
    public QueryFilter notin(String column,Collection value) {
        return op(column,SegmentCond.notin(value));
    }
    public QueryFilter notin(String column,Object value) {
        if(ObjectUtils.isEmpty(value))
            return this;
        if(value instanceof Collection)
            return notin(column,(Collection) value);
        else
            return notin(column,(String)value);
    }
    public QueryFilter like(String column,String value) {
        return op(column,SegmentCond.like(value));
    }
    public QueryFilter startsWith(String column,String value) {
        return op(column,SegmentCond.startsWith(value));
    }
    public QueryFilter endsWith(String column,String value) {
        return op(column,SegmentCond.endsWith(value));
    }
    public QueryFilter between(String column,Object from,Object to) {
        return op(column,SegmentCond.between(from,to));
    }
    public QueryFilter exist(String column,String value) {
        return op(column,SegmentCond.exists(value));
    }
    public QueryFilter notExist(String column,String value) {
        return op(column,SegmentCond.notExists(value));
    }

    public QueryFilter custom(String value) {
        return op("custom",SegmentCond.custom(value));
    }

    @JsonIgnore
    @Transient
    private Map<String,SegmentCond> map;

    private Map<String,SegmentCond> getMap()
    {
        if(map==null) {
            map=new LinkedHashMap<>();
        }
        return  map;
    }

    @JsonAnyGetter
    public Map<String , SegmentCond> any() {
        return getMap();
    }

    @JsonAnySetter
    public void set(String column, SegmentCond value) {
        getMap().put(column,value);
    }

    @JsonProperty(index = 999)
    private List<QueryFilter> $or;


    public List<QueryFilter> get$or() {
        return $or;
    }

    public void set$or(List<QueryFilter> $or) {
        this.$or = $or;
    }


    public QueryFilter or(QueryFilter... ors)
    {
        if ($or == null) {
            $or = new ArrayList();
        }
        Collections.addAll($or, ors);
        return this;

    }

    public QueryFilter or(List<QueryFilter> ors)
    {
        if ($or == null) {
            $or = new ArrayList();
        }
        $or.addAll(ors);
        return this;

    }


    @JsonProperty(index = 999)
    private List<QueryFilter> $and;

    public List<QueryFilter> get$and() {
        return $and;
    }

    public void set$and(List<QueryFilter> $and) {
        this.$and = $and;
    }

    public QueryFilter and(QueryFilter... ands)
    {
        if ($and == null) {
            $and = new ArrayList();
        }

        Collections.addAll($and, ands);
        return this;

    }

    public QueryFilter and(List<QueryFilter> ands)
    {
        if ($and == null) {
            $and = new ArrayList();
        }

        $and.addAll(ands);
        return this;

    }

    @Override
    public String toString() {
        return "QueryFilter{" +
                "map=" + map +
                ", $or=" + $or +
                '}';
    }

    public QueryFilter parse(Map<String,Object> objs) {
        objs.entrySet().forEach(item->{
            String input = item.getKey();
            //exists条件忽略
            if (input.contains("_exists__") || input.contains("_notexists__"))
                return;
            if(!input.startsWith("n_"))
                return;
            int firstIndex = input.indexOf("_");
            int lastIndex = input.lastIndexOf("_");

            if (firstIndex != -1 && lastIndex != -1 && firstIndex != lastIndex) {
                String column = input.substring(firstIndex + 1, lastIndex);
                op(column,SegmentCond.op(input,item.getValue()));
            }
        });
        return this;
    }

    private QueryFilter op(String column, SegmentCond segmentCond) {
        if(this.getMap().containsKey(column)) {
            ((SegmentCond)this.getMap().get(column)).getMap().putAll(segmentCond.getMap());
        }
        else {
            this.getMap().put(column,segmentCond);
        }
        return this;
    }

    public static class SegmentCond  {
        @JsonIgnore
        @Transient
        private Map<String,Object> map;

        @Override
        public String toString() {
            return "SegmentCond{" +
                    "map=" + map +
                    '}';
        }

        private Map<String,Object> getMap()
        {
            if(map==null) {
                map=new LinkedHashMap<>();
            }
            return  map;
        }

        @JsonAnyGetter
        public Map<String , Object> any() {
            return getMap();
        }

        @JsonAnySetter
        public void set(String column, Object value) {
            getMap().put(column,value);
        }

        public static SegmentCond eq(Object value) {
            return op(Segment.EQ,value);
        }
        public static SegmentCond ne(Object value) {
            return op(Segment.NE,value);
        }
        public static SegmentCond gt(Object value) {
            return op(Segment.GT,value);
        }
        public static SegmentCond ge(Object value) {
            return op(Segment.GE,value);
        }
        public static SegmentCond lt(Object value) {
            return op(Segment.LT,value);
        }
        public static SegmentCond le(Object value) {
            return op(Segment.LE,value);
        }
        public static SegmentCond isnull() {
            return op(Segment.IS_NULL,true);
        }
        public static SegmentCond isnotnull() {
            return op(Segment.IS_NOT_NULL,true);
        }
        public static SegmentCond in(Collection value) {
            return op(Segment.IN,value);
        }
        public static SegmentCond notin(Collection value) {
            return op(Segment.NOT_IN,value);
        }
        public static SegmentCond like(String value) {
            return op(Segment.LIKE,value);
        }
        public static SegmentCond startsWith(String value) {
            return op(Segment.LEFT_LIKE,value);
        }
        public static SegmentCond endsWith(String value) {
            return op(Segment.RIGHT_LIKE,value);
        }
        public static SegmentCond exists(String value) {
            return op(Segment.EXISTS,value);
        }
        public static SegmentCond notExists(String value) {
            return op(Segment.NOT_EXISTS,value);
        }
        public static SegmentCond custom(String value) {
            return op(Segment.CUSTOM,value);
        }
        public static SegmentCond between(Object from,Object to) {
            return op(Segment.GE,from).op(Segment.LT,to);
        }
        public static SegmentCond op(String segment,Object value) {
            return op(Segment.from(segment),value);
        }
        public static SegmentCond op(Segment segment,Object value) {
            SegmentCond segmentCond=new SegmentCond();
            segmentCond.getMap().put(segment.keyword,value);
            return segmentCond;
        }
    }

    public enum Segment  {
        AND("$and","and"),
        OR("$or","or"),

        EQ("$eq","eq"),
        NE("$ne","noteq"),
        GT("$gt","gt"),
        GE("$gte","gtandeq"),
        LT("$lt","lt"),
        LE("$lte","ltandeq"),
        IS_NULL("$null","isnull"),
        IS_NOT_NULL("$notNull","isnotnull"),
        IN("$in","in"),
        NOT_IN("$notIn","notin"),
        LIKE("$like","like"),
        LEFT_LIKE("$startsWith","leftlike"),
        RIGHT_LIKE("$endsWith","rightlike"),
        EXISTS("$exists","exists"),
        NOT_EXISTS("$notExists","notexists"),
        CUSTOM("$custom","custom");

        private final String keyword;

        private final String aliasKeyword;

        Segment(final String keyword,final String aliasKeyword) {
            this.keyword = keyword;
            this.aliasKeyword = aliasKeyword;
        }

        public static Segment from(String keyword) {
            return Arrays.stream(Segment.values()).filter(item -> item.keyword.equalsIgnoreCase(keyword)||keyword.toLowerCase().endsWith("_"+item.aliasKeyword.toLowerCase())||item.aliasKeyword.equalsIgnoreCase(keyword)).findFirst().orElseGet(()->EQ);
        }

        @Override
        public String toString() {
            return keyword;
        }

        public boolean equals(String codeName) {
            return this.keyword.equalsIgnoreCase(codeName)||this.aliasKeyword.endsWith("_"+keyword.toLowerCase());
        }

    }



}
